#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "FileData.h"

#include "Class.h"
#include "OrdColl.h"
#include "FileBuf.h"
#include "String.h"
#include "System.h"
#include "Directory.h"
#include "Converter.h"
#include "TypeMatcher.h"
#include "CType.h"
#include "File.h"
#include "ProgressFilter.h"

#include "ListSelectorDialog.h"

extern Converter *gEtConverter;
extern OrdCollection *gConverters;

enum ePathInfoFlags {
	ePiExec     =   1,
	ePiDir      =   2,
	ePiSpecial  =   4
};

const Symbol
	cDocTypeCPlusPlus("C++"),
	cDocTypeDirectory("DIRECTORY"),
	cDocTypeExec("EXEC"),
	cDocTypeOFile("OFILE");
		
#include "exec.xpm3"
SmartBitmap gExecIcon(exec);

#include "directory.xpm3"
SmartBitmap gDirectoryIcon(directory);

//---- CFileTypeMatcher --------------------------------------------------------

class CFileTypeMatcher: public TypeMatcher {
public:
	CFileTypeMatcher() : TypeMatcher(cDocTypeCPlusPlus, 0, 0, TRUE)
		{ }
	bool MatchExtension(Data *data, const char *ext);
	bool MatchNameAndContents(Data *data, const char *path, const char *buf, int len);
};

static CFileTypeMatcher cmatcher;

bool CFileTypeMatcher::MatchExtension(Data*, const char *ext)
{
	if (ext[1] == '\0' && strchr("cChH", ext[0]))
		return TRUE;
	if (strcmp(ext, "cp") == 0)
		return TRUE;
	if (strcmp(ext, "cxx") == 0)
		return TRUE;
	return FALSE;
}

bool CFileTypeMatcher::MatchNameAndContents(Data *data, const char *pathname,
								const char*, int)
{
	return MatchPathName(data, pathname, strlen(pathname));
}

//---- OFileTypeMatcher --------------------------------------------------------

class OFileTypeMatcher: public TypeMatcher {
public:
	OFileTypeMatcher() : TypeMatcher(cDocTypeOFile, 0, 0, FALSE)
		{ }
	bool MatchExtension(Data *data, const char *ext);
	bool MatchNameAndContents(Data *data, const char *path, const char *buf, int len);
};

static OFileTypeMatcher omatcher;

bool OFileTypeMatcher::MatchExtension(Data*, const char *ext)
{
	return (ext[0] == 'o' && ext[1] == '\0');
}

bool OFileTypeMatcher::MatchNameAndContents(Data *data, const char *pathname,
							const char*, int)
{
	return MatchPathName(data, pathname, strlen(pathname));
}

//---- DirectoryMatcher --------------------------------------------------------

class DirectoryMatcher: public TypeMatcher {
public:
	DirectoryMatcher() : TypeMatcher(cDocTypeDirectory, 0, 0, FALSE, 10000)
		{ }
	bool MatchNameAndContents(Data*, const char *pathname, const char*, int);
	Bitmap *GetBitmap()
		{ return gDirectoryIcon; }
	bool MatchPathName(Data*, const char *pathname, int len);
};

static DirectoryMatcher directorymatcher;

bool DirectoryMatcher::MatchPathName(Data *data, const char *pathname, int)
{
	return MatchNameAndContents(data, pathname, 0, 0);
}

bool DirectoryMatcher::MatchNameAndContents(Data*, const char *pathname, const char*, int)
{
	u_long flgs= 0;

	if (strcmp(pathname, ".") == 0 || strcmp(pathname, "..") == 0)
		return TRUE;

	if (gSystem->GetPathInfo(pathname, 0, 0, &flgs, 0) == 0) {
		if (flgs & ePiDir)
			return TRUE;
	}
	return FALSE;
}

//---- ExecMatcher -------------------------------------------------------------

class ExecMatcher: public TypeMatcher {
public:
	ExecMatcher() : TypeMatcher(cDocTypeExec, 0, 0, FALSE, 10001)
		{ }
	bool MatchNameAndContents(Data*, const char *pathname, const char*, int);
	Bitmap *GetBitmap()
		{ return gExecIcon; }
};

static ExecMatcher execmatcher;

bool ExecMatcher::MatchNameAndContents(Data*, const char *pathname, const char*, int)
{
	u_long flgs= 0;

	if (gSystem->GetPathInfo(pathname, 0, 0, &flgs, 0) == 0) {
		if ((flgs & ePiExec) && !(flgs & ePiDir))
			return TRUE;
	}
	return FALSE;
}

//---- FileData ----------------------------------------------------------------

NewMetaImpl(FileData, Data, (TP(converter), TB(lookDeep)));

static int untitledCnt;

FileData::FileData(const char *nm, bool deep) : Data(nm)
{
	lookDeep= deep;
	converter= 0;
	if (nm) {
		UpdateBit(eDataNew, FALSE);
	} else {
		strfreplace(&name, "untitled.%d", untitledCnt++);
		UpdateBit(eDataNew, TRUE);
	}
}

FileData::~FileData()
{
	converter= 0;
}

void FileData::SetName(const char *nm)
{
	flags= 0;
	if (nm) {
		strreplace(&name, nm);
		UpdateBit(eDataNew, FALSE);
	} else {
		strfreplace(&name, "untitled.%d", untitledCnt++);
		UpdateBit(eDataNew, TRUE);
	}
}

const char *FileData::FullName()
{
	if (!IsUntitled() && name[0] != '/') {
		const char *wd= gSystem->WorkingDirectory();
		if (strcmp(name, ".") == 0)
			strreplace(&name, wd);
		else {
			if (wd && strcmp(wd, "/"))
				strfreplace(&name, "%s/%s", wd, name);
			else
				strfreplace(&name, "/%s", name);
		}
	}
	return name;
}

const char *FileData::ShortName()
{
	return (char*) BaseName(FullName());
}

const char *FileData::GetLoadDir()
{
	return gSystem->DirName(FullName());
}

Converter *FileData::GetConverter()
{
	if (converter == 0)
		converter= gEtConverter;
	return converter;
}

void FileData::SetConverter(Converter *cv)
{
	converter= cv;
}

u_long FileData::UpdateFields(u_long f)
{
	if (!TESTBIT(flags, f)) {

		switch (f) {
		case eDataId:
		case eDataSize:
		case eDataTime:
		case eDataExec:
		case eDataValid:
			u_long ff;
			bool valid= gSystem->GetPathInfo(FullName(), &uniqueid,
				(u_long*)&sizehint, &ff, &modtime) == 0;
			SETBIT(flags, eDataId);
			SETBIT(flags, eDataSize);
			SETBIT(flags, eDataTime);
			UpdateBit(eDataExec, (u_int)(ff & ePiExec));
			UpdateBit(eDataValid, valid);
			break;
		
		case eDataType:
			if (lookDeep) {
				Data::UpdateFields(f);
			} else {
				typematcher= TypeMatcher::MatchByName(this);
				if (typematcher && type == cDocTypeUndef)
					type= typematcher->GetType();
				SETBIT(flags, eDataType);
				if (type == cDocTypeDirectory)
					SETBIT(flags, eDataDeepType);   // no need to look deeper
			}
			break;

		case eDataDeepType:
		case eDataCreator:
			Data::UpdateFields(f);
			break;

		case eDataRead:
			UpdateBit(eDataRead, !gSystem->AccessPathName(FullName(), 4));
			break;
		
		case eDataWrite:
			{
				const char *nm= FullName();
				if (gSystem->AccessPathName(nm, 0)) {
					char *p= strrchr(nm, '/'), buf[1000];
					if (p) {
						strcpy(buf, nm);
						p= strrchr(buf, '/');
						*p= 0;
					} else
						strcpy(buf, ".");
					nm= buf;
				}
				UpdateBit(eDataWrite, !gSystem->AccessPathName(nm, 2));
			}
			break;

		case eDataNew:
			//UpdateBit(eDataNew, *FullName() != '/');
			break;

		}
	}
	return 0;
}

Object *FileData::MakeObject(Class *want)
{
	if (converter == 0) {
		if (gConverters == 0 || gConverters->Size() <= 0) {
			if (gConverters == 0)
				gConverters= new OrdCollection;
			Converter::FindConverter(this, want, gConverters);
		}
		if (gConverters->Size() > 1)
			converter= (Converter*) ListSelectorDialog::Select(gConverters,
								"Select Converter");
		else if (gConverters->Size() == 1)
			converter= (Converter*) gConverters->At(0);
		else
			converter= 0;
	}
	SafeDelete(gConverters);
	if (converter && converter->CanConvert(this, want))
		return converter->Convert(this, want);
	return 0;
}

void FileData::StartUpdate()
{
	if (!IsUntitled() && IsReadable())
		gSystem->Exec(form("cp %s %s%s", FullName(), FullName(), "%"));
}

void FileData::EndUpdate(bool isok, bool removebackup)
{
	if (!isok)
		gSystem->Exec(form("cp %s%s %s", FullName(), "%", FullName()));
		
	if (isok && removebackup)
		File::Unlink(form("%s%s", FullName(), "%"));
}

StreamBuf *FileData::GetStreamBuf(bool forwriting)
{
	if (forwriting) {
		return new Filebuf(FullName(), output);
	} else {
		StreamBuf *osb= new Filebuf(FullName(), input);
		//StreamBuf *sb= new ProgressFilter;
		//sb->SetBaseStream(osb);
		//return sb;
		return osb;
	}
}

SeqCollection *FileData::GetContents()
{
	const char *nm, *fname= FullName();
	Directory dir(fname);
	OrdCollection *files= new OrdCollection;

	for (int i= 0; nm= dir(); i++)
		if (strcmp(nm, ".") != 0)
			files->Add(new FileData(form("%s/%s", fname, nm), lookDeep));

	return files;
}

Data *FileData::GetParent()
{
	char buf[200], *cp;
	const char *fname= FullName();
	if (strcmp(fname, "/") == 0)
		return 0;
	strcpy(buf, fname);
	if (cp= strrchr(buf, '/'))
		*cp= 0;
	if (cp == buf)
		return new FileData("/", lookDeep);
	return new FileData(buf, lookDeep);
}

OStream& FileData::PrintOn(OStream &s)
{
	Data::PrintOn(s);
	return s << lookDeep SP << (long)converter SP;
}

IStream& FileData::ReadFrom(IStream &s)
{
	long cv;
	Data::ReadFrom(s);
	s >> lookDeep >> cv;
	converter= (Converter*) cv;
	return s;
}

